#include "stdafx.h"
#include "DoomsdayDate.h"
#include <math.h>
#include <iostream>

namespace TimeZone
{
	bool weekdayIsValid(Weekday weekday) { return weekday >= Sunday && weekday <= Saturday; }
	bool monthIsValid(Month month) { return month >= Jan && month <= Dec; }
	bool yearIsValid(int year) { return year > 0; }
	bool dayIsValid(int day) { return day > 0 && day < 32; }
}

//--------------------------------------------------------------------------------
TimeZone::DoomsdayDate::DoomsdayDate(bool JulianBeforeGregorian)
	: m_useJulianBeforeGregorian(JulianBeforeGregorian)
	, m_valid(true)
	, m_gregorian(true)
	, m_ad(true)
	, m_month(InvalidMonth)
	, m_day(InvalidDay)
	, m_weekday(InvalidWeekday)
	, m_year(InvalidYear)
{}
//--------------------------------------------------------------------------------
TimeZone::DoomsdayDate::DoomsdayDate(int month, int day, int year, bool ad)
{
	Set(month, day, year, ad);
}
//--------------------------------------------------------------------------------
int TimeZone::DoomsdayDate::daysInMonth(void)
{
	if (!monthIsValid(m_month))
	{
		return 0;
	}

	switch (m_month)
	{
	case 4: // 30 day months
	case 6:
	case 9:
	case 11:
		return 30;
	case 2:
		return LeapYear(m_year) ? 29 : 28;
	}

	return 31;
}
//--------------------------------------------------------------------------------
bool TimeZone::DoomsdayDate::Set(int month, int day, int year, bool ad)
{
    m_valid = true;
	m_month = static_cast<Month>(month);
	m_day = day;
	m_year = year;
    if (!monthIsValid(static_cast<Month>(month)) || !dayIsValid(day) || !yearIsValid(year))
    {
        m_valid = false;
		return false;
	}
	if (!ad)
	{
		m_gregorian = false;
	}
	else
	{
		m_gregorian = Gregorian(month, day, year);
	}
	if (!m_valid)
	{
		return false;
	}
	switch (month)
	{
	case 4: // 30 day months
	case 6:
	case 9:
	case 11:
		if( day > 30 ) return false;
		break;
	case 2:
		if( (LeapYear(year) && day > 29) || (day > 28) ) return false;
		break;
	}
	m_ad = ad;
	m_valid = true;
	return m_valid;
}
//--------------------------------------------------------------------------------
bool TimeZone::DoomsdayDate::LeapYear(int year)
{
	bool r = false;
	int modFour = year % 4;
	int modHundred = year % 100;
	if (m_gregorian)
	{
		if (modFour == 0 && modHundred != 0 || modHundred == 0 && modHundred % 4 == 0)
		{
			r = true;
		}
	}
	return r;
}
//--------------------------------------------------------------------------------
bool TimeZone::DoomsdayDate::Gregorian(int month, int day, int year)
{
	bool r = false;
	if (year > 1582)
	{
		r = true;
	}
	else if (year < 1582)
	{
		r = false;
	}
	else // ( yyyy == 1582 )
	{
		if (month == 10)
		{
			if (day > 14)
			{
				r = true;
			}
			else if (day < 5)
			{
				r = false;
			}
			else
			{
                if (!m_useJulianBeforeGregorian)
                {
                    m_valid = true;
                }
                else
                {
                    // October 5-14, 1582 omitted from Gregorian calendar
                    m_valid = false; 
                }
				r = m_valid;
			}
		}
		else if (month < 10)
		{
			r = false;
		}
		else
		{
			r = true;
		}
	}
	return r;
}
//--------------------------------------------------------------------------------
TimeZone::Weekday TimeZone::DoomsdayDate::weekday()
{
	Weekday r = InvalidWeekday;
	int x = 0, y = 0;
	int ddcentury = -1;
	int ddmonth = DoomsdayMonth(m_month);
	if (m_gregorian) // Gregorian Calendar
	{
		int century = m_year - m_year % 100;
		ddcentury = DoomsdayCentury(century);
		if (ddcentury < 0 || ddmonth < 0)
		{
			return InvalidWeekday;
		}
		if (ddmonth > m_day)
		{
            m_weekday = static_cast<Weekday>(7 - ((ddmonth-m_day) % 7 ) + ddmonth);
		}
        else
        {
            m_weekday = static_cast<Weekday>(m_day);
        }
		x = m_weekday - ddmonth;
		x %= 7;
		y = ddcentury + (m_year - century) + int((m_year-century) / 4);
		y %= 7;
		r = static_cast<Weekday>((x + y) % 7);
	}
	else if (!m_ad) // B.C -> AD Julian
	{
		int dd = -1;
		if (m_year > 699)
		{
			dd = 701 - m_year % 700;
		}
		else
		{
			dd = 29 - m_year % 28;
		}
		if (dd > 0)
		{
			ddcentury = (((dd - dd % 100) / 100) * 6) % 7;
			x = (dd % 100) % 7 + int((dd % 100) / 4) % 7;					
			if (ddmonth > m_day)
			{
				y = ddmonth + m_day;
			}
			else
			{
				y = m_day - ddmonth;
			}
			y %= 7;
			x = ddcentury + x;
			x %= 7;
			r = static_cast<Weekday>((x + y) % 7);
		}
	}
	else // Julian Calendar
	{
		ddcentury = (((m_year - m_year % 100) / 100) * 6) % 7;
		x = (m_year % 100) % 7 + int((m_year % 100) / 4) % 7;					
		if (ddmonth > m_day)
		{
			y = ddmonth + m_day;
		}
		else
		{
			y = m_day - ddmonth;
		}
		y %= 7;
		x = ddcentury + x;
		x %= 7;
		r = static_cast<Weekday>((x + y) % 7);
	}
    m_weekday = r;
	return m_weekday;
}
//--------------------------------------------------------------------------------
int TimeZone::DoomsdayDate::DoomsdayMonth(Month month)
{
	int r = InvalidMonth;
	switch (month)
	{
    case Jan:
		r = LeapYear(m_year) ? 32 : 31;
		break;
    case Feb:
		r = LeapYear(m_year) ? 29 : 28;
		break;
    case Mar:
		r = 7;
		break;
    case Apr:
		r = 4;
		break;
    case May:
		r = 9;
		break;
    case Jun:
		r = 6;
		break;
    case Jul:
		r = 11;
		break;
    case Aug:
		r = 8;
		break;
    case Sep:
		r = 5;
		break;
    case Oct:
		r = 10;
		break;
    case Nov:
		r = 7;
		break;
    case Dec:
		r = 12;
		break;
	}
	return r;
}
//--------------------------------------------------------------------------------
int TimeZone::DoomsdayDate::DoomsdayCentury(int century)
{
	int r = -1;
	switch( century % 400 )
	{
	case 0:
		r = 2;
		break;
	case 100:
		r = 0;
		break;
	case 200:
		r = 5;
		break;
	case 300:
		r = 3;
		break;
	}
	return r;
}
//--------------------------------------------------------------------------------
// returns the day of n-th sunday in month
int TimeZone::DoomsdayDate::sundayDay(int nth_sunday_in_month)
{
	const int current_day = m_day;
	m_day = 1;
	const int weekday_of_1 = weekday();
	m_day = current_day;

	int result = nth_sunday_in_month * 7 - 6;

	if (weekday_of_1)
	{
		result += 7 - weekday_of_1 % 7;
	}

	const int days_in_month = daysInMonth();

	while (result > days_in_month)
	{
		result -= 7;
	}

	return result;
}
